#define _FILE_OFFSET_BITS 64

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/types.h>
#include <errno.h>
#include <aio.h>

#include "Util.h"

#define BLOCK_SIZE 44194304 // 4MB
#define WRITE_SIZE 4096

typedef enum {
	NORMAL, FALLOCATE
} writeType_t;

struct workLoadParams {
	int option;
	int fileDescriptor;
	long numWrites;
	char* fileName; // For display purpose
};

void* runWorkload(void* ptr);
struct workLoadParams* constructWorkLoadParams(int option, int fileDescriptor, long numWrites, char* fileName);
void doPreAllocatedWrite(int fileDescriptor, long numWrites, int doesPreAllocate);

long getRandomNumber();

int main(int argc, char *argv[]) {
	long numWrites;
	int option = 0, fileDescriptor1 = 0, fileDescriptor2 = 0;
	char *fileName1 = NULL, *fileName2 = NULL;
	writeType_t writeType;
    struct workLoadParams *params1, *params2;

    pthread_t writeThread1, writeThread2;

    validateAndFillInputArgumentsForFileSystemWorkload(argc, argv, &fileName1,
			"FileName1", &fileName2, "FileName2" , &numWrites, "NumWrites", &option, "Option");
    writeType = option;
	srand(time(NULL ));

	fileDescriptor1 = open(fileName1, O_CREAT|O_WRONLY|O_TRUNC);
	fileDescriptor2 = open(fileName2, O_CREAT|O_WRONLY|O_TRUNC);
    if(fileDescriptor1 == -1 || fileDescriptor2 == -1) {
    	printf("Unable to open file %s or %s\n", fileName1, fileName2);
    	perror("Checking the error");
    	exit(-1);
    }

    printf("Filename1 :%s\n", fileName1);
    printf("Filename2 :%s\n", fileName2);

    params1 = constructWorkLoadParams(option, fileDescriptor1, numWrites, fileName1);
    params2 = constructWorkLoadParams(option, fileDescriptor2, numWrites, fileName2);

	pthread_create( &writeThread1, NULL, runWorkload, (void*) params1);
	pthread_create( &writeThread2, NULL, runWorkload, (void*) params2);

	pthread_join( writeThread1, NULL);
	pthread_join( writeThread2, NULL);

	return EXIT_SUCCESS;
}

void* runWorkload(void* ptr) {
    double timeForWrites;
    struct timespec startTime, endTime;
    struct workLoadParams* param = (struct workLoadParams*) ptr;

    writeType_t writeType = param->option;
    int fileDescriptor = param->fileDescriptor;
    long numWrites = param->numWrites;

	clock_gettime(CLOCK_REALTIME, &startTime);
	switch (writeType) {
	case NORMAL:
		doPreAllocatedWrite(fileDescriptor, numWrites, 0);
		break;
	case FALLOCATE:
		doPreAllocatedWrite(fileDescriptor, numWrites, 1);
		break;
	default:
		printf("ERROR: WriteType is %d\n", writeType);
		exit(-1);
	}
	clock_gettime(CLOCK_REALTIME, &endTime);
	timeForWrites = getDiffInNanoSeconds(startTime, endTime);
	timeForWrites /= 10e6;

	printf("%s %lu %f\n", param->fileName, numWrites, (double) timeForWrites);
	return NULL;
}

void doPreAllocatedWrite(int fileDescriptor, long numWrites, int doesPreAllocate) {
	long length = numWrites * WRITE_SIZE;
	int fallocateResult = 0, counter = 0, writeResult = 0, fsyncResult = 0;
	char buffer[WRITE_SIZE];

	if (doesPreAllocate) {
		fallocateResult = fallocate(fileDescriptor, 0, 0, length);
		if (fallocateResult == -1) {
			printf("Error while doing fallocate for fd  %d\n", fileDescriptor);
			perror("ERROR During fallocate");
			exit(-1);
		}
		fsyncResult = fsync(fileDescriptor);
		if (fsyncResult == -1) {
			perror("Fsync Error");
			exit(-1);
		}
	}

	for (counter = 0; counter < numWrites; counter++) {
		fillAllocatedMemory(buffer, WRITE_SIZE);
		writeResult = write(fileDescriptor, buffer, WRITE_SIZE);
		if(writeResult == -1) {
			perror("Write Error");
			exit(-1);
		}
		if (counter % 100 == 0) {
			  fsyncResult = fsync(fileDescriptor);
					if (fsyncResult == -1) {
						perror("Fsync Error");
						exit(-1);
					}
			//usleep(1000 * 1);
		}
	}

	fsyncResult = fsync(fileDescriptor);
	if (fsyncResult == -1) {
		perror("Fsync Error");
		exit(-1);
	}

}

struct workLoadParams* constructWorkLoadParams(int option, int fileDescriptor, long numWrites, char* fileName) {
	struct workLoadParams *param = (struct workLoadParams*) malloc(sizeof(struct workLoadParams));
	param->option = option;
	param->fileDescriptor = fileDescriptor;
	param->numWrites = numWrites;
	param->fileName = fileName;
	printf("Option: %d \tFileDescriptor: %d \t numWrites :%lu fileName :%s\n", option, fileDescriptor, numWrites, fileName);
	return param;
}

long getRandomNumber() {
	uint64_t random =
	  (((uint64_t) rand() <<  0) & 0x00000000FFFFFFFFull) |
	  (((uint64_t) rand() << 32) & 0xFFFFFFFF00000000ull);
	return (long) random;
}
